﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ultima.Xna.Entities;

namespace Ultima.Xna.Net.Packets
{
    public class AsciiMessagePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _graphic;
        readonly byte _type;
        readonly short _hue;
        readonly short _font;
        readonly string _name;
        readonly string _text;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Graphic
        {
            get { return _graphic; }
        }

        public byte Type
        {
            get { return _type; }
        }

        public short Hue
        {
            get { return _hue; }
        }

        public short Font
        {
            get { return _font; }
        }

        public string Name1
        {
            get { return _name; }
        }

        public string Text
        {
            get { return _text; }
        }


        public AsciiMessagePacket(PacketReader reader)
            : base(0x1C, "Ascii Message")
        {
            _serial = reader.ReadInt32();
            _graphic = reader.ReadInt16();
            _type = reader.ReadByte();
            _hue = reader.ReadInt16();
            _font = reader.ReadInt16();
            _name = reader.ReadString(30);
            _text = reader.ReadString();
        }
    }

    public class CharacterCityListPacket : RecvPacket
    {
        readonly StartingLocation[] _locations;
        readonly CharacterListEntry[] _characters;

        public StartingLocation[] Locations
        {
            get { return _locations; }
        }

        public CharacterListEntry[] Characters
        {
            get { return _characters; }
        }

        public CharacterCityListPacket(PacketReader reader)
            : base(0xA9, "Char/City List")
        {
            int characterCount = reader.ReadByte();
            _characters = new CharacterListEntry[characterCount];

            for (int i = 0; i < characterCount; i++)
            {
                _characters[i] = new CharacterListEntry(reader);
            }

            int locationCount = reader.ReadByte();
            _locations = new StartingLocation[locationCount];

            for (int i = 0; i < locationCount; i++)
            {
                _locations[i] = new StartingLocation(reader);
            }
        }
    }

    public class ChatPacket : RecvPacket
    {
        readonly string _language;
        readonly byte _commandtype;

        public string Language
        {
            get { return _language; }
        }

        public byte CommandType
        {
            get { return _commandtype; }
        }

        public ChatPacket(PacketReader reader)
            : base(0xB3, "Chat Packet")
        {

            _language = reader.ReadString(3);
            reader.ReadInt16(); // unknown.
            _commandtype = reader.ReadByte();
        }
    }

    public class ContainerContentPacket : RecvPacket
    {
        private ContentItem[] _items;

        public ContentItem[] Items
        {
            get { return _items; }
            set { _items = value; }
        }

        public ContainerContentPacket(PacketReader reader)
            : base(0x3C, "Container ContentPacket")
        {
            int itemCount = reader.ReadUInt16();
            List<ContentItem> items = new List<ContentItem>(itemCount);

            for (int i = 0; i < itemCount; i++)
            {
                Serial serial = reader.ReadInt32();
                int iItemID = reader.ReadUInt16();
                int iUnknown = reader.ReadByte(); // signed, itemID offset. always 0 in RunUO.
                int iAmount = reader.ReadUInt16();
                int iX = reader.ReadInt16();
                int iY = reader.ReadInt16();
                int iGridLocation = reader.ReadByte(); // always 0 in RunUO.
                int iContainerGUID = reader.ReadInt32();
                int iHue = reader.ReadUInt16();

                items.Add(new ContentItem(serial, iItemID, iAmount, iX, iY, iGridLocation, iContainerGUID, iHue));
            }

            _items = items.ToArray();
        }
    }

    public class ContainerContentUpdatePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly int _itemId;
        readonly int _amount;
        readonly int _x;
        readonly int _y;
        readonly int _gridLocation;
        readonly int _containerSerial;
        readonly int _hue;

        public int ItemId
        {
            get { return _itemId; }
        }

        public int Amount
        {
            get { return _amount; }
        }

        public int X
        {
            get { return _x; }
        }

        public int Y
        {
            get { return _y; }
        }

        public int GridLocation
        {
            get { return _gridLocation; }
        }

        public int ContainerSerial
        {
            get { return _containerSerial; }
        }

        public int Hue
        {
            get { return _hue; }
        }

        public Serial Serial
        {
            get { return _serial; }
        }

        public ContainerContentUpdatePacket(PacketReader reader)
            : base(0x25, "Add Single Item")
        {
            _serial = reader.ReadInt32();
            _itemId = reader.ReadUInt16();
            reader.ReadByte(); // unknown 
            _amount = reader.ReadUInt16();
            _x = reader.ReadInt16();
            _y = reader.ReadInt16();
            _gridLocation = reader.ReadByte(); // always 0 in RunUO.
            _containerSerial = reader.ReadInt32();
            _hue = reader.ReadUInt16();
        }
    }

    public class OpenContainerPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly ushort _gumpId;

        public Serial Serial { get { return _serial; } }
        public ushort GumpId { get { return _gumpId; } }

        public OpenContainerPacket(PacketReader reader)
            : base(0x24, "Open Container")
        {
            this._serial = reader.ReadInt32();
            this._gumpId = reader.ReadUInt16();
        }
    }

    public class DamagePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _damage;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Damage
        {
            get { return _damage; }
        }

        public DamagePacket(PacketReader reader)
            : base(0x0B, "Damage")
        {
            _serial = reader.ReadInt32();
            _damage = reader.ReadInt16();
        }
    }

    public class DragEffectPacket : RecvPacket
    {
        readonly int _itemId;
        readonly int _amount;
        readonly Serial _sourceContainer;
        readonly int _sourceX;
        readonly int _sourceY;
        readonly int _sourceZ;
        readonly Serial _destContainer;
        readonly int _destX;
        readonly int _destY;
        readonly int _destZ;

        public int ItemId
        {
            get { return _itemId; }
        }

        public int Amount
        {
            get { return _itemId; }
        }

        public Serial SourceContainer
        {
            get { return _sourceContainer; }
        }

        public int SourceX
        {
            get { return _sourceX; }
        }

        public int SourceY
        {
            get { return _sourceY; }
        }

        public int SourceZ
        {
            get { return _sourceZ; }
        }

        public Serial DestContainer
        {
            get { return _destContainer; }
        }

        public int DestX
        {
            get { return _destX; }
        }

        public int DestY
        {
            get { return _destY; }
        }

        public int DestZ
        {
            get { return _destZ; }
        }

        public DragEffectPacket(PacketReader reader)
            : base(0x23, "Dragging Item")
        {
            _itemId = reader.ReadUInt16();
            reader.ReadByte(); // 0x03 bytes unknown.
            reader.ReadByte(); //
            reader.ReadByte(); //
            _amount = reader.ReadUInt16();
            _sourceContainer = reader.ReadInt32(); // 0xFFFFFFFF for ground
            _sourceX = reader.ReadUInt16();
            _sourceY = reader.ReadUInt16();
            _sourceZ = reader.ReadByte();
            _destContainer = reader.ReadInt32(); // 0xFFFFFFFF for ground
            _destX = reader.ReadUInt16();
            _destY = reader.ReadUInt16();
            _destZ = reader.ReadByte();
        }
    }

    public struct EquipmentEntry
    {
        public Serial Serial;
        public ushort GumpId;
        public byte Layer;
        public ushort Hue;

        public EquipmentEntry(Serial serial, ushort gumpId, byte layer, ushort hue)
        {
            Serial = serial;
            GumpId = gumpId;
            Layer = layer;
            Hue = hue;
        }
    }

    public class GeneralInfoPacket : RecvPacket
    {
        readonly short _subcommand;
        readonly ContextMenu _contextmenu;
        readonly byte _mapid;

        public short Subcommand
        {
            get { return _subcommand; }
        }

        public ContextMenu ContextMenu
        {
            get { return _contextmenu; }
        }

        public byte MapID
        {
            get { return _mapid; }
        }

        public GeneralInfoPacket(PacketReader reader)
            : base(0xBF, "General Information")
        {
            this._subcommand = reader.ReadInt16();

            switch (this._subcommand)
            {
                case 0x8: // Set cursor color / set map
                    _mapid = reader.ReadByte();
                    break;
                case 0x14: // return context menu
                    _contextmenu = new ContextMenu();
                    m_ReceiveContextMenu(reader);
                    break;
                case 0x18: // Number of maps
                    // !!! Not implemented yet.
                    break;
                case 0x1D: // House revision
                    // !!! Not implemented yet.
                    break;
                case 0x04: // Close generic gump
                    // !!! Not implemented yet.
                    break;
                default:
                    // do nothing. This unhandled subcommand will raise an error in UltimaClient.cs.
                    break;
            }
        }

        private void m_ReceiveContextMenu(PacketReader reader)
        {
            reader.ReadByte(); // unknown (0x00)
            int iSubCommand = reader.ReadByte(); // 0x01 for 2D, 0x02 for KR
            Serial Serial = reader.ReadInt32();
            int iNumEntriesInContext = reader.ReadByte();

            for (int i = 0; i < iNumEntriesInContext; i++)
            {
                int iUniqueID = reader.ReadUInt16();
                int iClilocID = reader.ReadUInt16() + 3000000;
                int iFlags = reader.ReadUInt16(); // 0x00=enabled, 0x01=disabled, 0x02=arrow, 0x20 = color
                int iColor = 0;
                if ((iFlags & 0x20) == 0x20)
                {
                    iColor = reader.ReadUInt16();
                }
                _contextmenu.AddItem(iUniqueID, iClilocID, iFlags, iColor);
            }
        }
    }

    public class LiftRejectionPacket : RecvPacket
    {
        private static string[] _reasons = new string[] {
              "Cannot lift the item.",
              "Out of range.",
              "Out of sight.",
              "Belongs to another.",
              "Already holding something.",
              "???",
            };

        readonly byte _errorCode;

        public byte ErrorCode
        {
            get { return _errorCode; }
        }

        public string ErrorMessage
        {
            get { return _reasons[_errorCode]; }
        }

        public LiftRejectionPacket(PacketReader reader)
            : base(0x27, "Request Move Item Request")
        {
            _errorCode = reader.ReadByte();
        }
    }

    public class LoginCompletePacket : RecvPacket
    {
        public LoginCompletePacket(PacketReader reader)
            : base(0x55, "Login Complete")
        {

        }
    }

    public class LoginConfirmPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _body;
        readonly short _x;
        readonly short _y;
        readonly short _z;
        readonly byte _direction;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Body
        {
            get { return _body; }
        }

        public short X
        {
            get { return _x; }
        }

        public short Y
        {
            get { return _y; }
        }

        public short Z
        {
            get { return _z; }
        }

        public byte Direction
        {
            get { return _direction; }
        }

        public LoginConfirmPacket(PacketReader reader)
            : base(0x1B, "Login Confirm")
        {
            _serial = reader.ReadInt32();

            reader.ReadInt32();//unknown..

            _body = reader.ReadInt16();
            _x = reader.ReadInt16();
            _y = reader.ReadInt16();
            _z = reader.ReadInt16();
            _direction = reader.ReadByte();
        }
    }

    public class LoginRejectionPacket : RecvPacket
    {
        public static string[] Reasons = new string[] {
              "Incorrect username and/or password.",
              "Someone is already using this account.",
              "Your account has been blocked.",
              "Your account credentials are invalid.",
              "Communication problem.",
              "The IGR concurrency limit has been met.",
              "The IGR time limit has been met.",
              "General IGR authentication failure."
            };

        byte _id;

        public string Reason
        {
            get { return LoginRejectionPacket.Reasons[_id]; }
        }

        public LoginRejectionPacket(PacketReader reader)
            : base(0x82, "Login Rejection")
        {
            _id = reader.ReadByte();
        }
    }

    public class MobileAttributesPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _maxHits;
        readonly short _currentHits;
        readonly short _maxMana;
        readonly short _currentMana;
        readonly short _maxStamina;
        readonly short _currentStamina;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short MaxHits
        {
            get { return _maxHits; }
        }

        public short CurrentHits
        {
            get { return _currentHits; }
        }

        public short MaxMana
        {
            get { return _maxMana; }
        }

        public short CurrentMana
        {
            get { return _currentMana; }
        }

        public short MaxStamina
        {
            get { return _maxStamina; }
        }

        public short CurrentStamina
        {
            get { return _currentStamina; }
        }


        public MobileAttributesPacket(PacketReader reader)
            : base(0x2D, "Mobile Attributes")
        {
            _serial = reader.ReadInt32();
            _maxHits = reader.ReadInt16();
            _currentHits = reader.ReadInt16();
            _maxMana = reader.ReadInt16();
            _currentMana = reader.ReadInt16();
            _maxStamina = reader.ReadInt16();
            _currentStamina = reader.ReadInt16();
        }
    }

    public class MobileIncomingPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _body;
        readonly short _x;
        readonly short _y;
        readonly short _z;
        readonly byte _direction;
        readonly ushort _hue;
        readonly byte _flags;
        readonly byte _notoriety;
        readonly EquipmentEntry[] _equipment;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short BodyID
        {
            get { return _body; }
        }

        public short X
        {
            get { return _x; }
        }

        public short Y
        {
            get { return _y; }
        }

        public short Z
        {
            get { return _z; }
        }

        public byte Direction
        {
            get { return _direction; }
        }

        public ushort Hue
        {
            get { return _hue; }
        }

        public EquipmentEntry[] Equipment
        {
            get { return _equipment; }
        }

        /// <summary>
        /// These are the only flags sent by RunUO
        /// 0x02 = female
        /// 0x04 = poisoned
        /// 0x08 = blessed/yellow health bar
        /// 0x40 = warmode
        /// 0x80 = hidden
        /// </summary>
        public byte Flags
        {
            get { return _flags; }
        }

        /// <summary>
        /// 0x1: Innocent (Blue)
        /// 0x2: Friend (Green)
        /// 0x3: Grey (Grey - Non Criminal)
        /// 0x4: Criminal (Grey)
        /// 0x5: Enemy (Orange)
        /// 0x6: Murderer (Red)
        /// 0x7: Invulnerable (Yellow)
        /// </summary>
        public byte Notoriety
        {
            get { return _notoriety; }
        }

        public MobileIncomingPacket(PacketReader reader)
            : base(0x78, "Mobile Incoming")
        {
            // Mobile
            _serial = reader.ReadInt32();
            _body = reader.ReadInt16();
            _x = reader.ReadInt16();
            _y = reader.ReadInt16();
            _z = reader.ReadSByte();
            _direction = reader.ReadByte();
            _hue = reader.ReadUInt16();
            _flags = reader.ReadByte();
            _notoriety = reader.ReadByte();

            // Read equipment - nine bytes ea.
            int equipCount = (reader.Size - reader.Index) / 9;
            _equipment = new EquipmentEntry[equipCount];

            for (int i = 0; i < equipCount; i++)
            {
                int serial = reader.ReadInt32();

                ushort gumpId = reader.ReadUInt16();
                byte layer = reader.ReadByte();
                ushort hue = 0;

                if ((gumpId & 0x8000) == 0x8000)
                {
                    gumpId = (ushort)((int)gumpId - 0x8000);
                    hue = reader.ReadUInt16();
                }

                _equipment[i] = new EquipmentEntry(serial, gumpId, layer, hue);
            }

            reader.ReadByte(); //zero terminated
        }
    }

    public class MobileMovingPacket : RecvPacket
    {
        readonly Serial serial;
        readonly ushort bodyid;
        readonly short x;
        readonly short y;
        readonly sbyte z;
        readonly byte direction;
        readonly ushort hue;
        readonly byte flags;
        readonly byte notoriety;

        public Serial Serial
        {
            get { return serial; }
        }

        public ushort BodyID
        {
            get { return bodyid; }
        }

        public short X
        {
            get { return x; }
        }

        public short Y
        {
            get { return y; }
        }

        public sbyte Z
        {
            get { return z; }
        }

        public byte Direction
        {
            get { return direction; }
        }

        public ushort Hue
        {
            get { return hue; }
        }

        public byte Flags
        {
            get { return flags; }
        }

        /// <summary>
        /// These are the only flags sent by RunUO
        /// 0x02 = female
        /// 0x04 = poisoned
        /// 0x08 = blessed/yellow health bar
        /// 0x40 = warmode
        /// 0x80 = hidden
        //// </summary>
        public byte Notoriety
        {
            get { return notoriety; }
        }

        public MobileMovingPacket(PacketReader reader)
            : base(0x77, "Mobile Moving")
        {
            this.serial = reader.ReadInt32();
            this.bodyid = reader.ReadUInt16();
            this.x = reader.ReadInt16();
            this.y = reader.ReadInt16();
            this.z = reader.ReadSByte();
            this.direction = reader.ReadByte();
            this.hue = reader.ReadUInt16();
            this.flags = reader.ReadByte();
            this.notoriety = reader.ReadByte();
        }
    }

    public class MobileUpdatePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _body;
        readonly short _x;
        readonly short _y;
        readonly short _z;
        readonly byte _direction;
        readonly ushort _hue;
        readonly byte flags;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short BodyID
        {
            get { return _body; }
        }

        public short X
        {
            get { return _x; }
        }

        public short Y
        {
            get { return _y; }
        }

        public short Z
        {
            get { return _z; }
        }

        public byte Direction
        {
            get { return _direction; }
        }

        public ushort Hue
        {
            get { return _hue; }
        }

        /// <summary>
        /// These are the only flags sent by RunUO
        /// 0x02 = female
        /// 0x04 = poisoned
        /// 0x08 = blessed/yellow health bar
        /// 0x40 = warmode
        /// 0x80 = hidden
        /// </summary>
        public byte Flags
        {
            get { return flags; }
        }

        public MobileUpdatePacket(PacketReader reader)
            : base(0x20, "Mobile Update")
        {
            _serial = reader.ReadInt32();
            _body = reader.ReadInt16();
            reader.ReadByte(); // Always 0
            _hue = reader.ReadUInt16(); // Skin hue
            flags = reader.ReadByte();
            _x = reader.ReadInt16();
            _y = reader.ReadInt16();
            reader.ReadInt16(); // Always 0
            _direction = reader.ReadByte();
            _z = reader.ReadSByte();
        }
    }

    public class MoveAcknowledgePacket : RecvPacket
    {
        readonly byte _sequence;
        readonly byte _notoriety;

        public byte Sequence
        {
            get { return _sequence; }
        }

        public byte Notoriety
        {
            get { return _notoriety; }
        }

        public MoveAcknowledgePacket(PacketReader reader)
            : base(0x22, "Move Request Acknowledged")
        {
            _sequence = reader.ReadByte(); // (matches sent sequence)
            _notoriety = reader.ReadByte(); // Not sure why it sends this.
        }
    }

    public class MovementRejectPacket : RecvPacket
    {
        readonly byte _sequence;
        readonly short _x;
        readonly short _y;
        readonly byte _direction;
        readonly byte _z;

        public byte Sequence
        {
            get { return _sequence; }
        }

        public short X
        {
            get { return _x; }
        }

        public short Y
        {
            get { return _y; }
        }

        public byte Direction
        {
            get { return _direction; }
        }

        public byte Z
        {
            get { return _z; }
        }

        public MovementRejectPacket(PacketReader reader)
            : base(0x21, "Move Request Rejected")
        {
            _sequence = reader.ReadByte(); // (matches sent sequence)
            _x = reader.ReadInt16();
            _y = reader.ReadInt16();
            _direction = reader.ReadByte();
            _z = reader.ReadByte();
        }
    }

    public class WorldItemPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _itemid;
        readonly short _amount;
        readonly short _x;
        readonly short _y;
        readonly sbyte _z;
        readonly byte _direction;
        readonly ushort _hue;
        readonly byte _flags;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short ItemID
        {
            get { return _itemid; }
        }

        public short StackAmount
        {
            get { return _amount; }
        }

        public short X
        {
            get { return _x; }
        }

        public short Y
        {
            get { return _y; }
        }

        public short Z
        {
            get { return _z; }
        }

        public byte Direction
        {
            get { return _direction; }
        }

        public ushort Hue
        {
            get { return _hue; }
        }

        public short Flags
        {
            get { return _flags; }
        }

        public WorldItemPacket(PacketReader reader)
            : base(0x1A, "ObjectInfo")
        {
            uint serial = reader.ReadUInt32();
            ushort itemId = reader.ReadUInt16();

            _amount = 0;

            if ((serial & 0x80000000) == 0x80000000)
            {
                _amount = reader.ReadInt16();
            }

            // Doesn't exist this thing in the packet
            /*byte iIncrement = 0;
            if ((iItemID & 0x8000) == 0x8000)
            {
                iIncrement = reader.ReadByte();
                iObjectSerial += iIncrement;
            }*/

            ushort x = reader.ReadUInt16();
            ushort y = reader.ReadUInt16();

            _direction = 0;

            if ((x & 0x8000) == 0x8000)
                _direction = reader.ReadByte();

            _z = reader.ReadSByte();
            _hue = 0;

            if ((y & 0x8000) == 0x8000)
                _hue = reader.ReadUInt16();

            _flags = 0;

            if ((y & 0x4000) == 0x4000)
                _flags = reader.ReadByte();

            _serial = (int)(serial &= 0x7FFFFFFF);
            _itemid = (short)(itemId &= 0x7FFF);
            _x = (short)(x &= 0x7FFF);
            _y = (short)(y &= 0x3FFF);
        }
    }

    public class ObjectPropertyListPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly int _hash;
        readonly List<int> _clilocs;
        readonly List<string> _arguments;

        public Serial Serial
        {
            get { return _serial; }
        }

        public int Hash
        {
            get { return _hash; }
        }

        public List<int> CliLocs
        {
            get { return _clilocs; }
        }

        public List<string> Arguements
        {
            get { return _arguments; }
        }

        public ObjectPropertyListPacket(PacketReader reader)
            : base(0xD6, "Object Property List")
        {
            reader.ReadInt16(); // Always 0x0001
            _serial = reader.ReadInt32();

            reader.ReadInt16(); // Always 0x0000
            _hash = reader.ReadInt32();

            _clilocs = new List<int>();
            _arguments = new List<string>();

            // Loop of all the item/creature's properties to display in the order to display them. The name is always the first entry.
            int clilocId = reader.ReadInt32();

            while (clilocId != 0)
            {
                _clilocs.Add(clilocId);

                int textLength = reader.ReadUInt16();
                string args = string.Empty;

                if (textLength > 0)
                {
                    args = reader.ReadUnicodeStringReverse(textLength / 2);
                }

                _arguments.Add(args);

                clilocId = reader.ReadInt32();
            }
        }
    }

    public class ObjectPropertyListUpdatePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly int _revisionHash;

        public Serial Serial
        {
            get { return _serial; }
        }

        public int RevisionHash
        {
            get { return _revisionHash; }
        }

        public ObjectPropertyListUpdatePacket(PacketReader reader)
            : base(0xDC, "Object Property List Update")
        {
            _serial = reader.ReadInt32();
            _revisionHash = reader.ReadInt32();
        }
    }

    public class OverallLightLevelPacket : RecvPacket
    {
        readonly byte _lightLevel;

        public byte LightLevel
        {
            get { return _lightLevel; }
        }

        public OverallLightLevelPacket(PacketReader reader)
            : base(0x4F, "OverallLightLevel")
        {
            this._lightLevel = reader.ReadByte();
        }
    }

    public class PersonalLightLevelPacket : RecvPacket
    {
        readonly Serial _creatureSerial;
        readonly byte _lightLevel;

        public Serial CreatureSerial
        {
            get { return _creatureSerial; }
        }

        public byte LightLevel
        {
            get { return _lightLevel; }
        }

        public PersonalLightLevelPacket(PacketReader reader)
            : base(0x4E, "Personal Light Level")
        {
            this._creatureSerial = reader.ReadInt32();
            this._lightLevel = reader.ReadByte();
        }
    }

    public class PlayerMovePacket : RecvPacket
    {
        readonly byte _direction;

        public byte Direction
        {
            get { return _direction; }
        }

        public PlayerMovePacket(PacketReader reader)
            : base(0x97, "Player Move")
        {
            _direction = reader.ReadByte();
        }
    }

    public class PopupMessagePacket : RecvPacket
    {
        public static string[] Messages = new string[] {
                "Character does not exist",
		        "Character already exists",
		        "A character is already logged in",
		        "Loggin sync error",
		        "You have been idle for to long"
            };

        readonly byte _id;

        public string Message
        {
            get
            {
                switch (_id)
                {
                    case 1:
                        return Messages[0];
                    case 2:
                        return Messages[1];
                    case 5:
                        return Messages[2];
                    case 6:
                        return Messages[3];
                    case 7:
                        return Messages[4];
                    default:
                        return "Error: No message defined!";
                }
            }
        }

        public PopupMessagePacket(PacketReader reader)
            : base(0x53, "Popup Message")
        {
            _id = reader.ReadByte();
        }
    }

    public class RemoveEntityPacket : RecvPacket
    {
        readonly Serial _serial;

        public Serial Serial
        {
            get { return _serial; }
        }

        public RemoveEntityPacket(PacketReader reader)
            : base(0x1D, "Remove Entity")
        {
            _serial = reader.ReadInt32();
        }
    }

    public class RequestNameResponsePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly string _mobileName;

        public Serial Serial
        {
            get { return _serial; }
        }

        public string MobileName
        {
            get { return _mobileName; }
        }

        public RequestNameResponsePacket(PacketReader reader)
            : base(0x98, "Request Name Response")
        {
            _serial = reader.ReadInt32();
            _mobileName = reader.ReadString(30);
        }
    }

    public class SeasonChangePacket : RecvPacket
    {
        readonly byte _season;
        readonly byte _playSound;

        public byte Season
        {
            get { return _season; }
        }
        public byte PlaySound
        {
            get { return _playSound; }
        }

        public SeasonChangePacket(PacketReader reader)
            : base(0xBC, "Seasonal Information")
        {
            this._season = reader.ReadByte();
            this._playSound = reader.ReadByte();
        }
    }

    public class ServerListPacket : RecvPacket
    {
        readonly byte _flags;
        readonly ServerListEntry[] _servers;

        public byte Flags
        {
            get { return _flags; }
        }

        public ServerListEntry[] Servers
        {
            get { return _servers; }
        }

        public ServerListPacket(PacketReader reader)
            : base(0xA8, "Server List")
        {
            _flags = reader.ReadByte();
            ushort count = (ushort)reader.ReadInt16();

            _servers = new ServerListEntry[count];

            for (ushort i = 0; i < count; i++)
            {
                _servers[i] = new ServerListEntry(reader); ;
            }
        }
    }

    public class ServerRelayPacket : RecvPacket
    {
        readonly int _ipAddress;
        readonly int _port;
        readonly int _accountId;

        public int IpAddress
        {
            get { return _ipAddress; }
        }

        public int Port
        {
            get { return _port; }
        }

        public int AccountId
        {
            get { return _accountId; }
        }

        public ServerRelayPacket(PacketReader reader)
            : base(0x8C, "Server Relay")
        {
            _ipAddress = reader.ReadInt32();
            _port = reader.ReadInt16();
            _accountId = reader.ReadInt32();
        }
    }

    public class MobileStatusCompactPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly string _playerName;
        readonly short _currentHealth;
        readonly short _maxHealth;
        readonly byte _nameChangeFlag;
        readonly byte _statusTypeFlag;
        readonly Sex sex;
        readonly short _strength;
        readonly short _dexterity;
        readonly short _intelligence;
        readonly short _currentStamina;
        readonly short _maxStamina;
        readonly short _currentMana;
        readonly short _maxMana;
        readonly int _goldInInventory;
        readonly short _armorRating;
        readonly short _weight;
        readonly short _maxWeight;
        readonly Race _race;
        readonly short _statCap;
        readonly byte _followers;
        readonly byte _maxFollowers;
        readonly short _resistFire;
        readonly short _resistCold;
        readonly short _resistPoison;
        readonly short _resistEnergy;
        readonly short _luck;
        readonly short _dmgMin;
        readonly short _dmgMax;
        readonly short _tithingPoints;

        public Serial Serial
        {
            get { return _serial; }
        }

        public string PlayerName
        {
            get { return _playerName; }
        }

        public short CurrentHealth
        {
            get { return _currentHealth; }
        }

        public short MaxHealth
        {
            get { return _maxHealth; }
        }

        public bool NameChangeFlag
        {
            get { return (_nameChangeFlag == 0x01); }
        }

        public byte StatusType
        {
            get { return _statusTypeFlag; }
        }

        public Sex Sex
        {
            get { return sex; }
        }

        public short Strength
        {
            get { return _strength; }
        }

        public short Dexterity
        {
            get { return _dexterity; }
        }

        public short Intelligence
        {
            get { return _intelligence; }
        }

        public short CurrentStamina
        {
            get { return _currentStamina; }
        }

        public short MaxStamina
        {
            get { return _maxStamina; }
        }

        public short CurrentMana
        {
            get { return _currentMana; }
        }

        public short MaxMana
        {
            get { return _maxMana; }
        }

        public int GoldInInventory
        {
            get { return _goldInInventory; }
        }

        public short ArmorRating
        {
            get { return _armorRating; }
        }

        public short Weight
        {
            get { return _weight; }
        }

        public short MaxWeight
        {
            get { return _maxWeight; }
        }

        public Race Race
        {
            get { return _race; }
        }

        public short StatCap
        {
            get { return _statCap; }
        }

        public byte Followes
        {
            get { return _followers; }
        }

        public byte MaxFollowers
        {
            get { return _maxFollowers; }
        }

        public short ResistFire
        {
            get { return _resistFire; }
        }

        public short ResistCold
        {
            get { return _resistCold; }
        }

        public short ResistPoison
        {
            get { return _resistPoison; }
        }

        public short ResistEnergy
        {
            get { return _resistEnergy; }
        }

        public short Luck
        {
            get { return _luck; }
        }

        public short DamageMin
        {
            get { return _dmgMin; }
        }

        public short DamageMax
        {
            get { return _dmgMax; }
        }

        public short TithingPoints
        {
            get { return _tithingPoints; }
        }

        public MobileStatusCompactPacket(PacketReader reader)
            : base(0x11, "StatusInfo")
        {
            _serial = reader.ReadInt32();
            _playerName = reader.ReadString(30);
            _currentHealth = reader.ReadInt16();
            _maxHealth = reader.ReadInt16();
            _nameChangeFlag = reader.ReadByte(); // 0x1 = allowed, 0 = not allowed
            _statusTypeFlag = reader.ReadByte();
            sex = (Sex)reader.ReadByte(); // 0=male, 1=female
            _strength = reader.ReadInt16();
            _dexterity = reader.ReadInt16();
            _intelligence = reader.ReadInt16();
            _currentStamina = reader.ReadInt16();
            _maxStamina = reader.ReadInt16();
            _currentMana = reader.ReadInt16();
            _maxMana = reader.ReadInt16();
            _goldInInventory = reader.ReadInt32();
            _armorRating = reader.ReadInt16();
            _weight = reader.ReadInt16();

            if (_statusTypeFlag >= 5)
            {
                _maxWeight = reader.ReadInt16();
                _race = (Race)reader.ReadByte();
            }

            if (_statusTypeFlag >= 3)
            {
                _statCap = reader.ReadInt16();
                _followers = reader.ReadByte();
                _maxFollowers = reader.ReadByte();
            }

            if (_statusTypeFlag >= 4)
            {
                _resistFire = reader.ReadInt16();
                _resistCold = reader.ReadInt16();
                _resistPoison = reader.ReadInt16();
                _resistEnergy = reader.ReadInt16();
                _luck = reader.ReadInt16();
                _dmgMin = reader.ReadInt16();
                _dmgMax = reader.ReadInt16();
                _tithingPoints = reader.ReadInt16();
            }
        }
    }

    public class SubServerPacket : RecvPacket
    {
        readonly short _x;
        readonly short _y;
        readonly short _z;
        readonly short _mapWidth;
        readonly short _mapHeight;

        public short X
        {
            get { return _x; }
        }

        public short Y
        {
            get { return _y; }
        }

        public short Z
        {
            get { return _z; }
        }

        public short MapWidth
        {
            get { return _mapWidth; }
        }

        public short MapHeight
        {
            get { return _mapHeight; }
        }

        public SubServerPacket(PacketReader reader)
            : base(0xB3, "Chat Packet")
        {
            _x = reader.ReadInt16();
            _y = reader.ReadInt16();
            _z = reader.ReadInt16();
            reader.ReadByte();
            reader.ReadInt16();
            reader.ReadInt16();
            _mapWidth = reader.ReadInt16();
            _mapHeight = reader.ReadInt16();
        }
    }

    public class SupportedFeaturesPacket : RecvPacket
    {
        readonly short _flags;

        /// <summary>
        /// 0x01: enable T2A features: chat, regions
        /// 0x02: enable renaissance features
        /// 0x04: enable third dawn features
        /// 0x08: enable LBR features: skills, map
        /// 0x10: enable AOS features: skills, map, spells, fightbook
        /// 0x20: 6th character slot
        /// 0x40: enable SE features
        /// 0x80: enable ML features: elven race, spells, skills
        /// 0x100: enable 8th age splash screen
        /// 0x200: enable 9th age splash screen
        /// 0x1000: 7th character slot
        /// 0x2000: 10th age KR faces
        /// </summary>
        public short Flags
        {
            get { return _flags; }
        }

        public SupportedFeaturesPacket(PacketReader reader)
            : base(0xB9, "Enable Features")
        {
            _flags = reader.ReadInt16();
        }
    }

    public class SwingPacket : RecvPacket
    {
        readonly Serial _attacker;
        readonly Serial _defender;
        readonly byte _flag;

        public Serial Attacker
        {
            get { return _attacker; }
        }

        public Serial Defender
        {
            get { return _defender; }
        }

        public byte Flag
        {
            get { return _flag; }
        }

        public SwingPacket(PacketReader reader)
            : base(0x2F, "Swing")
        {
            _flag = reader.ReadByte();
            _attacker = reader.ReadInt32();
            _defender = reader.ReadInt32();
        }
    }

    public class TargetCursorPacket : RecvPacket
    {
        readonly byte _commandtype;
        readonly int _cursorid;
        readonly byte _cursortype;

        public byte CommandType
        {
            get { return _commandtype; }
        }

        public int CursorID
        {
            get { return _cursorid; }
        }

        public byte CursorType
        {
            get { return _cursortype; }
        }

        public TargetCursorPacket(PacketReader reader)
            : base(0x6C, "Target Cursor")
        {
            _commandtype = reader.ReadByte(); // 0x00 = Select Object; 0x01 = Select X, Y, Z
            _cursorid = reader.ReadInt32();
            _cursortype = reader.ReadByte(); // 0 - 2 = unknown; 3 = Cancel current targetting RunUO seems to always send 0.
        }
    }

    public class UnicodeMessagePacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _model;
        readonly byte _msgType;
        readonly short _hue;
        readonly short _font;
        readonly int _language;
        readonly string _speakerName;
        readonly string _spokenText;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Model
        {
            get { return _model; }
        }

        public byte MsgType
        {
            get { return _msgType; }
        }

        public short Hue
        {
            get { return _hue; }
        }

        public short Font
        {
            get { return _font; }
        }

        public int Language
        {
            get { return _language; }
        }

        public string SpeakerName
        {
            get { return _speakerName; }
        }

        public string SpokenText
        {
            get { return _spokenText; }
        }

        public UnicodeMessagePacket(PacketReader reader)
            : base(0xAE, "Unicode Message")
        {
            _serial = reader.ReadInt32();
            _model = reader.ReadInt16();
            _msgType = reader.ReadByte();
            _hue = reader.ReadInt16();
            _font = reader.ReadInt16();
            _language = reader.ReadInt32();
            _speakerName = reader.ReadString(30);
            _spokenText = reader.ReadUnicodeString((reader.Buffer.Length - 48) / 2);
        }
    }

    public class UOGInfoResponsePacket : RecvPacket
    {
        readonly string _name;
        readonly int _age;
        readonly int _clientCount;
        readonly int _itemCount;
        readonly int _mobileCount;
        readonly string _memory;

        public int Age
        {
            get { return _age; }
        }

        public int ClientCount
        {
            get { return _clientCount; }
        }

        public int ItemCount
        {
            get { return _itemCount; }
        }

        public int MobileCount
        {
            get { return _mobileCount; }
        }

        public string ServerName
        {
            get { return _name; }
        }

        public string Memory
        {
            get { return _memory; }
        }

        public UOGInfoResponsePacket(PacketReader reader)
            : base(0x52, "UOG Information Response")
        {
            string response = reader.ReadString();
            string[] parts = response.Split(',');

            for (int j = 0; j < parts.Length; j++)
            {
                string[] keyValue = parts[j].Split('=');

                if (keyValue.Length == 2)
                {
                    string key = keyValue[0].Trim();
                    string value = keyValue[1].Trim();

                    switch (key)
                    {
                        case "Name":
                            _name = value;
                            break;
                        case "Age":
                            _age = int.Parse(value);
                            break;
                        case "Clients":
                            _clientCount = int.Parse(value) - 1;
                            break;
                        case "Items":
                            _itemCount = int.Parse(value);
                            break;
                        case "Chars":
                            _mobileCount = int.Parse(value);
                            break;
                        case "Mem":
                            _memory = value;
                            break;
                    }
                }
            }
        }
    }

    public class UpdateHealthPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _current;
        readonly short _max;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Current
        {
            get { return _current; }
        }

        public short Max
        {
            get { return _max; }
        }

        public UpdateHealthPacket(PacketReader reader)
            : base(0xA1, "Update Health")
        {
            _serial = reader.ReadInt32();
            _max = reader.ReadInt16();
            _current = reader.ReadInt16();
        }
    }

    public class UpdateManaPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _current;
        readonly short _max;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Current
        {
            get { return _current; }
        }

        public short Max
        {
            get { return _max; }
        }

        public UpdateManaPacket(PacketReader reader)
            : base(0xA2, "Update Mana")
        {
            _serial = reader.ReadInt32();
            _max = reader.ReadInt16();
            _current = reader.ReadInt16();
        }
    }

    public class UpdateStaminaPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _current;
        readonly short _max;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short Current
        {
            get { return _current; }
        }

        public short Max
        {
            get { return _max; }
        }

        public UpdateStaminaPacket(PacketReader reader)
            : base(0xA3, "Update Stamina")
        {
            _serial = reader.ReadInt32();
            _max = reader.ReadInt16();
            _current = reader.ReadInt16();
        }
    }

    public class VendorBuyListPacket : RecvPacket
    {
        readonly Serial _vendorPackSerial;
        readonly int _itemCount;
        readonly int[] _prices;
        readonly string[] _descriptions;

        public Serial VendorPackSerial
        {
            get { return _vendorPackSerial; }
        }

        public int ItemCount
        {
            get { return _vendorPackSerial; }
        }

        public int[] Prices
        {
            get { return _prices; }
        }

        public string[] Descriptions
        {
            get { return _descriptions; }
        }

        public VendorBuyListPacket(PacketReader reader)
            : base(0x74, "Open Buy Window")
        {
            _vendorPackSerial = reader.ReadInt32();
            _itemCount = reader.ReadByte();
            _prices = new int[_itemCount];
            _descriptions = new string[_itemCount];

            for (int i = 0; i < this._itemCount; i++)
            {
                _prices[i] = reader.ReadInt32();
                int descriptionLegnth = reader.ReadByte();
                _descriptions[i] = reader.ReadString(descriptionLegnth);
            }
        }
    }

    public class VersionRequestPacket : RecvPacket
    {
        public VersionRequestPacket(PacketReader reader)
            : base(0xBD, "Client Version Request")
        {
            // no data.
        }
    }

    public class WarModePacket : RecvPacket
    {
        readonly byte _warmode;

        public bool WarMode
        {
            get { return (_warmode == 0x01); }
        }

        public WarModePacket(PacketReader reader)
            : base(0x72, "Request War Mode")
        {
            _warmode = reader.ReadByte();

            reader.ReadByte(); // always 0x00
            reader.ReadByte(); // always 0x32
            reader.ReadByte(); // always 0x00
        }
    }

    public class WeatherPacket : RecvPacket
    {
        readonly byte _weatherType;
        readonly byte _effectId;
        readonly byte _temperature;

        public byte WeatherType
        {
            get { return _weatherType; }
        }

        public byte NumberOfWeatherEffectsOnScreen
        {
            get { return _effectId; }
        }

        public byte Temperature
        {
            get { return _temperature; }
        }

        public WeatherPacket(PacketReader reader)
            : base(0x65, "Set Weather")
        {
            _weatherType = reader.ReadByte();
            _effectId = reader.ReadByte();
            _temperature = reader.ReadByte();
        }
    }
    
    public class WornItemPacket : RecvPacket
    {
        readonly Serial _serial;
        readonly short _itemId;
        readonly byte _layer;
        readonly Serial _parentSerial;
        readonly short _hue;

        public Serial Serial
        {
            get { return _serial; }
        }

        public short ItemId
        {
            get { return _itemId; }
        }

        public byte Layer
        {
            get { return _layer; }
        }

        public Serial ParentSerial
        {
            get { return _parentSerial; }
        }

        public short Hue
        {
            get { return _hue; }
        }


        public WornItemPacket(PacketReader reader)
            : base(0x2E, "Worn Item")
        {
            _serial = reader.ReadInt32();
            _itemId = reader.ReadInt16();
            reader.ReadByte();
            _layer = reader.ReadByte();
            _parentSerial = reader.ReadInt32();
            _hue = reader.ReadInt16();
        }
    }
}
